package com.ubi.core.swagger.reader;

import com.google.common.base.Function;
import com.google.common.base.Optional;
import com.mangofactory.swagger.models.Annotations;
import com.mangofactory.swagger.models.dto.AllowableListValues;
import com.mangofactory.swagger.models.dto.AllowableValues;
import com.mangofactory.swagger.models.dto.Parameter;
import com.wordnik.swagger.annotations.ApiModelProperty;
import com.wordnik.swagger.annotations.ApiParam;

import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.List;

import static com.google.common.base.Optional.fromNullable;
import static com.google.common.base.Strings.emptyToNull;
import static com.google.common.base.Strings.isNullOrEmpty;
import static com.google.common.collect.Lists.transform;
import static com.mangofactory.swagger.models.property.ApiModelProperties.allowableValueFromString;

/**
 * @author leiyi
 * @date 2016-04-26 17:13
 * @summary:CustomerParameterBuilder
 */
public class CustomerParameterBuilder {

    private String dataTypeName;
    private String parentName;
    private Field field;

    public CustomerParameterBuilder withDataTypeName(String dataTypeName) {
        this.dataTypeName = dataTypeName;
        return this;
    }

    public CustomerParameterBuilder withParentName(String parentName) {
        this.parentName = parentName;
        return this;
    }

    public CustomerParameterBuilder forField(Field field) {
        this.field = field;
        return this;
    }

    public Parameter build() {
        Optional<ApiModelProperty> apiModelPropertyOptional = Annotations.findApiModePropertyAnnotation(field);
        if (apiModelPropertyOptional.isPresent()) {
            if (apiModelPropertyOptional.get().hidden()) {
                return null;
            }
            return fromApiModelProperty(apiModelPropertyOptional.get());
        }
        Optional<ApiParam> apiParamOptional = Annotations.findApiParamAnnotation(field);
        if (apiParamOptional.isPresent()) {
            return fromApiParam(apiParamOptional.get());
        }
        return defaultParameter();
    }

    private Parameter defaultParameter() {
        AllowableValues allowable = allowableValues(Optional.<String>absent(), field);

        return new com.mangofactory.swagger.models.dto.builder.ParameterBuilder()
                .name(isNullOrEmpty(parentName) ? field.getName() : String.format("%s.%s", parentName, field.getName()))
                .description(null).defaultValue(null)
                .required(Boolean.FALSE)
                .allowMultiple(Boolean.FALSE)
                .dataType(dataTypeName)
                .allowableValues(allowable)
                .parameterType("query")
                .parameterAccess(null)
                .build();

    }

    private Parameter fromApiParam(ApiParam apiParam) {
        String allowableProperty = emptyToNull(apiParam.allowableValues());
        AllowableValues allowable = allowableValues(fromNullable(allowableProperty), field);

        return new com.mangofactory.swagger.models.dto.builder.ParameterBuilder()
                .name(isNullOrEmpty(parentName) ? field.getName() : String.format("%s.%s", parentName, field.getName()))
                .description(apiParam.value())
                .defaultValue(apiParam.defaultValue())
                .required(apiParam.required())
                .allowMultiple(apiParam.allowMultiple())
                .dataType(dataTypeName)
                .allowableValues(allowable)
                .parameterType("query")
                .parameterAccess(apiParam.access())
                .build();
    }

    private Parameter fromApiModelProperty(ApiModelProperty apiModelProperty) {
        String allowableProperty = emptyToNull(apiModelProperty.allowableValues());
        AllowableValues allowable = allowableValues(fromNullable(allowableProperty), field);
        return new com.mangofactory.swagger.models.dto.builder.ParameterBuilder()
                .name(isNullOrEmpty(parentName) ? translate(field.getName()) : translate(String.format("%s.%s", parentName, field.getName())))
                .description(apiModelProperty.value())
                .defaultValue(null)
                .required(apiModelProperty.required())
                .allowMultiple(Boolean.FALSE)
                .dataType(dataTypeName)
                .allowableValues(allowable).parameterType("query")
                .parameterAccess(apiModelProperty.access())
                .build();
    }

    private AllowableValues allowableValues(final Optional<String> optionalAllowable, final Field field) {

        AllowableValues allowable = null;
        if (field.getType().isEnum()) {
            allowable = new AllowableListValues(getEnumValues(field.getType()), "LIST");
        } else if (optionalAllowable.isPresent()) {
            allowable = allowableValueFromString(optionalAllowable.get());
        }

        return allowable;
    }

    private List<String> getEnumValues(final Class<?> subject) {
        return transform(Arrays.asList(subject.getEnumConstants()), new Function<Object, String>() {
            @Override
            public String apply(final Object input) {
                return input.toString();
            }
        });
    }

    private String translate(String input) {
        if (input == null) return input; // garbage in, garbage out
        int length = input.length();
        StringBuilder result = new StringBuilder(length * 2);
        int resultLength = 0;
        boolean wasPrevTranslated = false;
        for (int i = 0; i < length; i++) {
            char c = input.charAt(i);
            if (i > 0 || c != '_') {
                if (Character.isUpperCase(c)) {
                    if (!wasPrevTranslated && resultLength > 0 && result.charAt(resultLength - 1) != '_')
                    {
                        result.append('_');
                        resultLength++;
                    }
                    c = Character.toLowerCase(c);
                    wasPrevTranslated = true;
                } else {
                    wasPrevTranslated = false;
                }
                result.append(c);
                resultLength++;
            }
        }
        return resultLength > 0 ? result.toString() : input;
    }
}
